package com.atguigu.gmall.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.model.product.*;
import com.atguigu.gmall.product.mapper.*;
import com.atguigu.gmall.product.mapper.BaseAttrInfoMapper;
import com.atguigu.gmall.product.service.ItemService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class ItemServiceImpl implements ItemService {

    @Autowired
    private SkuInfoMapper skuInfoMapper;

    @Autowired
    private SkuImageMapper skuImageMapper;

    @Autowired
    private SkuSaleAttrValueMapper skuSaleAttrValueMapper;

    @Autowired
    private BaseCategoryViewMapper baseCategoryViewMapper;

    @Autowired
    private SpuSaleAttrMapper spuSaleAttrMapper;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Autowired
    private BaseTrademarkMapper baseTrademarkMapper;
    @Autowired
    private BaseAttrInfoMapper baseAttrInfoMapper;
    /**
     * 查询sku的基本信息和图片信息
     *
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfo(Long skuId) {
        //查询sku的基本信息
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);

        if(skuInfo==null||skuInfo.getId()==null){
          return  null;
        }

        //查询图片信息
        List<SkuImage> list =
                skuImageMapper.selectList(new LambdaQueryWrapper<SkuImage>().eq(SkuImage::getSkuId, skuId));
        //将图片信息保存到skuinfo对象中去
        skuInfo.setSkuImageList(list);
        //返回
        return skuInfo;
    }

    /**方案一:lua+redis的分布式锁
     * 查询sku的基本信息和图片信息,优先从redis中获取，若redis中没有，
     * 从数据库与中获取，并将查询出来的数据缓存在redis中
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfoDBOrRedis(Long skuId) {
        //从redis中获取商品的数据
          //1.定义skuInfo的key
          String skuKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
          //2.从redis中获取商品的数据
        String skuString = stringRedisTemplate.boundValueOps(skuKey).get();
        //判断数据
        if(StringUtils.isEmpty(skuString)){
              //随机生成一个uuid
            String uuid = UUID.randomUUID().toString();
            //若redis中为空，加上一把锁
            String lockKey = RedisConst.SKUKEY_PREFIX+skuId+RedisConst.SKUKEY_SUFFIX;
            //去数据中查询sku的数据，查询完成以后，将数据缓存到redis中去.
            Boolean lockResult = stringRedisTemplate.boundValueOps(lockKey).setIfAbsent(uuid, 3, TimeUnit.SECONDS);
            if (lockResult){
                SkuInfo skuInfo = getSkuInfo(skuId);
                //判断
                if(skuInfo==null||skuInfo.getId()==null){
                    skuInfo = new SkuInfo();
                    stringRedisTemplate.boundValueOps(skuKey).set(JSONObject.toJSONString(skuInfo),300,TimeUnit.SECONDS);
                }else{
                    //sku不为空，存入redis
                    stringRedisTemplate.boundValueOps(skuKey).set(JSONObject.toJSONString(skuInfo),RedisConst.SECKILL__TIMEOUT,TimeUnit.SECONDS);
                }
                //释放锁
                DefaultRedisScript<Long> script = new DefaultRedisScript<>();
                String lua = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //设置脚本
                script.setScriptText(lua);
                //设置返回值
                script.setResultType(Long.class);
                //执行释放锁
                stringRedisTemplate.execute(script, Arrays.asList(lockKey), uuid);
                //返回结果
                return skuInfo;
            }else{

             try{
                 //加锁失败的请求，就进行sleep(1000)，再去查询一次
                 Thread.sleep(1000);
                 return  getSkuInfoDBOrRedis(skuId);
             }catch (Exception e){
                 e.printStackTrace();
             }
          }
        }else{
            //redis有值，直接返回
            return JSONObject.parseObject(skuString,SkuInfo.class);
        }

        return null;
    }
    /**方案二:redission的分布式锁
     * 查询sku的基本信息和图片信息,优先从redis中获取，若redis中没有，
     * 从数据库与中获取，并将查询出来的数据缓存在redis中
     * @param skuId
     * @return
     */
    @Override
    public SkuInfo getSkuInfoFromDBOrRedisRedission(Long skuId) {
        //定义skuinfo的key:假设现在skuId=123  ---> skuKey=sku:123:info
        String skuKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKUKEY_SUFFIX;
        //从redis中获取商品的数据
        String skuString = stringRedisTemplate.boundValueOps(skuKey).get();
        //判断数据是否为空
        if(StringUtils.isEmpty(skuString)){
            //定义锁的key= sku:123:lock
            String lockKey = RedisConst.SKUKEY_PREFIX + skuId + RedisConst.SKULOCK_SUFFIX;
            //生成一把锁
            RLock lock = redissonClient.getLock(lockKey);
            try {
                //尝试加锁
                if(lock.tryLock(100, 3, TimeUnit.SECONDS)){
                    //去数据库中查询sku的数据,查询完成以后,将数据缓存到redis中去
                    SkuInfo skuInfo = getSkuInfo(skuId);
                    //判断:数据是否为空----防止击穿情况出现
                    if(skuInfo == null || skuInfo.getId() == null){
                        //初始化对象
                        skuInfo = new SkuInfo();
                        //缓存空值到redis中
                        stringRedisTemplate
                                .boundValueOps(skuKey)
                                .set(JSONObject.toJSONString(skuInfo), 300, TimeUnit.SECONDS);
                    }else{
                        //如果sku不为空的时候,存入redis
                        stringRedisTemplate
                                .boundValueOps(skuKey)
                                .set(JSONObject.toJSONString(skuInfo), RedisConst.SKUKEY_TIMEOUT, TimeUnit.SECONDS);
                    }
                    return skuInfo;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                //释放锁
                lock.unlock();
            }
        }else{
            //redis有值,直接返回
            return JSONObject.parseObject(skuString, SkuInfo.class);
        }
        return null;
    }

    /**
     * 根据3级分类查询一级二级三级分类的详细信息
     *
     * @param cId
     * @return
     */
    @Override
    public BaseCategoryView getCategory(Long cId) {
        return baseCategoryViewMapper.selectById(cId);
    }

    /**
     * 获取商品的价格
     *
     * @param skuId
     * @return
     */
    @Override
    public BigDecimal getPrice(Long skuId) {
        //查询sku的基本信息
        SkuInfo skuInfo = skuInfoMapper.selectById(skuId);
        //获取价格返回
        return skuInfo.getPrice();
    }

    /**
     * 通过skuid和spuid查询当前商品的销售属性和当前spu的所有的销售属性
     *
     * @param skuId
     * @param spuId
     * @return
     */
    @Override
    public List<SpuSaleAttr> getSaleAttrBySpuAndSku(Long skuId, Long spuId) {
        return spuSaleAttrMapper.selectSaleAttrBySpuAndSku(skuId, spuId);
    }


    /**
     * 根据spuid查询出该spu下所有的sku与销售属性值的对应关系
     *
     * @param spuId
     * @return
     */
    @Override
    public Map getSkuSaleAttrListBySpuId(Long spuId) {
        Map<Object, Object> result = new HashMap<>();
        //查询结果
        List<Map> maps = skuSaleAttrValueMapper.selectSkuSaleAttrListBySpuId(spuId);
        //遍历保存
        for (Map map : maps) {
            result.put(map.get("sku_id"), map.get("value_id"));
        }
        return result;
    }

    @Override
    public List<JSONObject> getCategoryInfoList() {
        //返回结果初始化
        List<JSONObject> categoryList = new ArrayList<>();
        //查询出所有的分类的信息
        List<BaseCategoryView> baseCategoryViews = baseCategoryViewMapper.selectList(null);
        //处理一级二级三级分类
        Map<Long, List<BaseCategoryView>> category1Map = baseCategoryViews.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory1Id));
        //遍历
        for (Map.Entry<Long, List<BaseCategoryView>> category1Entry : category1Map.entrySet()) {
            JSONObject category1Json = new JSONObject();
            //获取一级分类的id
            Long category1Id = category1Entry.getKey();
            //获取一级分类的对应的所有二级分类
            List<BaseCategoryView> category2ViewList = category1Entry.getValue();
            //获取一级分类的名字
            String category1Name = category2ViewList.get(0).getCategory1Name();
            Map<Long, List<BaseCategoryView>> category2Map = category2ViewList.stream().collect(Collectors.groupingBy(BaseCategoryView::getCategory2Id));
            List<JSONObject> category2List = new ArrayList<>();
            for (Map.Entry<Long, List<BaseCategoryView>> category2Entry : category2Map.entrySet()) {
               JSONObject category2Json = new JSONObject();
               //获取二级分类的id
                Long category2Id = category2Entry.getKey();
                //获取所有的三级分类的列表
                List<BaseCategoryView> category3ViewList = category2Entry.getValue();
                //获取二级分类的名字
                String category2Name = category3ViewList.get(0).getCategory2Name();
                //保存所有的三级分类的信息
                List<JSONObject> category3List = new ArrayList<>();
                for (BaseCategoryView baseCategoryView : category3ViewList) {
                    JSONObject category3Json = new JSONObject();
                    //设置类别id
                    category3Json.put("categoryId", baseCategoryView.getCategory3Id());
                    //设置类别的名字
                    category3Json.put("categoryName", baseCategoryView.getCategory3Name());
                    //加入到列表中去
                    category3List.add(category3Json);
                }
                //设置类别id
                category2Json.put("categoryId",category2Id);
                //设置类别的名字
                category2Json.put("categoryName",category2Name);
                //设置二级分类对应的三级分类
                category2Json.put("categoryChild",category3List);
                //保存到列表
                category2List.add(category2Json);
            }
           //设置类别id
            category1Json.put("categoryId",category1Id);
            //设置类别id
            category1Json.put("categoryName",category1Name);
            //设置一级分类对应的二级分类
            category1Json.put("categoryChild",category2List);
            //保存到列表
            categoryList.add(category1Json);
        }
        return categoryList;
    }

    /**
     *
     * @param id
     * @return
     */
    @Override
    public BaseTrademark getById(Long id) {
        return  baseTrademarkMapper.selectById(id);

    }
    /**
     * 根据sku的id查询平台属性信息
     * @param skuId
     * @return
     */
    @Override
    public List<BaseAttrInfo> selectBaseAttrInfoBySku(Long skuId) {
        return baseAttrInfoMapper.selectBaseAttrInfoBySku(skuId);
    }
}
